/*
 * Copyright (c) 2009, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tssp_internal.h"
#include "tssp_table.h"
#include "tssp_dvb_ait.h"


struct tssp_dvb_ait_monitor {
    struct tssp_table_monitor *tmon;
    tssp_dvb_ait_monitor_callback callback;
    void *userdata;
};

struct tssp_dvb_ait *tssp_dvb_ait_parse(const struct tssp_section *section)
{
    int ret;
    struct tssp_dvb_ait *ait = NULL;
    struct tssp_dvb_ait_app *app;
    uint16_t length;
    uint8_t *pos;
    uint16_t loop_length;
    uint32_t org_id;
    uint16_t app_id;
    uint8_t control_code;
    struct tssp_table_payload p;

    TSSP_ASSERT(section);

    if (section->table_id != TSSP_TABLE_ID_DVB_AIT || !section->syntax_indicator)
        return NULL;

    /* prepare data buffer */
    ret = tssp_table_get_payload(&p, section, 0);
    if (ret != TSSP_STATUS_OK)
        return NULL;
    pos = p.start;

    if ((pos + 4) > p.end)
        goto cleanup;

    /* allocate ait */
    ait = tssp_dvb_ait_create();
    if (ait == NULL)
        goto cleanup;

    ait->test_flag = (0x8000 & section->extension) ? 1 : 0;
    ait->application_type = 0x7FFF & section->extension;
    ait->version = section->version;
    ait->current_next = section->current_next;

    length = ((0x0F & pos[0]) << 8) + pos[1]; /* common_descriptors_length */
    pos += 2;

    /* common descriptors */
    if (tssp_descriptor_parse(&ait->descriptors, pos, p.end, length) < 0)
        goto cleanup;
    pos += length;

    /* application loop */
    loop_length = ((0x0F & pos[0]) << 8) + pos[1];
    pos += 2;
    if ((p.end - pos) != loop_length)
        goto cleanup;

    while (loop_length >= 9) {
        /* store app params */
        org_id = (pos[0] << (8 * 3)) + (pos[1] << (8 * 2))
               + (pos[2] << (8 * 1)) + (pos[3] << (8 * 0));
        app_id = (pos[4] << 8) + pos[5];
        control_code = pos[6];
        length = ((pos[7] & 0x0F) << 8) + pos[8];
        pos += 9;

        /* create tssp_dvb_ait_app */
        app = tssp_dvb_ait_add_app(ait, org_id, app_id, control_code);
        if (app == NULL)
            goto cleanup;

        /* application descriptors */
        if (tssp_descriptor_parse(&app->descriptors, pos, p.end, length) < 0)
            goto cleanup;

        pos += length;
        loop_length -= (9 + length);
    }

    tssp_table_free_payload(&p);
    return ait;

cleanup:
    tssp_dvb_ait_destroy(ait);
    tssp_table_free_payload(&p);
    return NULL;
}

struct tssp_dvb_ait *tssp_dvb_ait_create()
{
    struct tssp_dvb_ait *ait = malloc(sizeof(*ait));

    if (ait) {
        STAILQ_INIT(&ait->descriptors);
        STAILQ_INIT(&ait->apps);
    }

    return ait;
}

void tssp_dvb_ait_destroy(struct tssp_dvb_ait *ait)
{
    struct tssp_dvb_ait_app *tmp;

    if (ait) {
        tssp_descriptors_destroy(&ait->descriptors);
        while ((tmp = STAILQ_FIRST(&ait->apps))) {
            STAILQ_REMOVE_HEAD(&ait->apps, entry);
            tssp_dvb_ait_app_destroy(tmp);
        }
        free(ait);
    }
}

struct tssp_dvb_ait_app *tssp_dvb_ait_add_app(struct tssp_dvb_ait *ait,
                                              uint32_t org_id,
                                              uint16_t app_id,
                                              uint8_t control_code)
{
    struct tssp_dvb_ait_app *app;

    TSSP_ASSERT(ait);

    app = malloc(sizeof(*app));
    if (app) {
        app->org_id = org_id;
        app->app_id = app_id;
        app->control_code = control_code;
        STAILQ_INIT(&app->descriptors);
        STAILQ_INSERT_TAIL(&ait->apps, app, entry);
    }

    return app;
}

void tssp_dvb_ait_app_destroy(struct tssp_dvb_ait_app *ait_app)
{
    if (ait_app) {
        tssp_descriptors_destroy(&ait_app->descriptors);
        free(ait_app);
    }
}


static
void tssp_dvb_ait_monitor_section_callback(struct tssp_table_monitor *tmon,
                                           struct tssp_section *section,
                                           void *userdata)
{
    struct tssp_dvb_ait_monitor *mon = userdata;
    struct tssp_dvb_ait *ait;

    ait = tssp_dvb_ait_parse(section);
    if (ait)
        mon->callback(mon, ait, section, mon->userdata);
    else
        tssp_section_destroy(section);
}

struct tssp_dvb_ait_monitor *
tssp_dvb_ait_monitor_attach(struct tssp *parser,
                            uint16_t pid,
                            uint16_t application_type,
                            tssp_dvb_ait_monitor_callback callback,
                            void *userdata)
{
    struct tssp_dvb_ait_monitor *monitor;
    struct tssp_section_filter filter;

    if (parser == NULL || callback == NULL)
        return NULL;

    monitor = malloc(sizeof(*monitor));
    if (monitor == NULL)
        return NULL;

    tssp_section_filter_init(&filter, pid, TSSP_TABLE_ID_DVB_AIT,
                             0x7FFF, application_type, 1, 0);
    monitor->tmon = tssp_table_monitor_attach(parser,
                                              &filter,
                                              tssp_dvb_ait_monitor_section_callback,
                                              monitor);
    if (monitor->tmon == NULL) {
        free(monitor);
        return NULL;
    }

    monitor->callback = callback;
    monitor->userdata = userdata;

    return monitor;
}

void tssp_dvb_ait_monitor_detach(struct tssp_dvb_ait_monitor *monitor)
{
    if (monitor == NULL)
        return;

    TSSP_ASSERT(monitor->tmon);

    tssp_dvb_ait_monitor_reset(monitor);
    tssp_table_monitor_detach(monitor->tmon);
    free(monitor);
}

void tssp_dvb_ait_monitor_reset(struct tssp_dvb_ait_monitor *mon)
{
    TSSP_ASSERT(mon);
    tssp_table_monitor_reset(mon->tmon);
}

